package edu.washington.cs.workflow.recommender;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.uispec4j.MenuItem;
import org.uispec4j.Panel;
import org.uispec4j.Trigger;
import org.uispec4j.UIComponent;
import org.uispec4j.UISpec4J;
import org.uispec4j.Window;
import org.uispec4j.interception.WindowHandler;
import org.uispec4j.interception.WindowInterceptor;

import edu.washington.cs.workflow.analysis.guistructure.MainWindowCreator;
import edu.washington.cs.workflow.analysis.guistructure.Spec4JUtils;
import edu.washington.cs.workflow.analysis.guistructure.UIMenuPos;
import edu.washington.cs.workflow.analysis.guistructure.UIPos;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEvent;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEvent.WINDOW;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEventFactory;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEventOutcome;
import edu.washington.cs.workflow.util.Utils;

/**
 * Implements a recommender based on the REST approach. For each
 * type of GUI element, it finds the element with identical label
 * in the new version as the replacement UI action.
 * 
 * Input: a label and the GUI element (optionally)
 *        the main class and args of the updated app version
 * */
public class RESTBasedRecommender {
	
	static {
		UISpec4J.init();
	}
	
	//argument to launch the updated app
	public final String mainClassName;
	public final Class<?> mainClass;
	public final String[] args;
	
	//the label to find an UI element
	public final String label;
	private String type = null;
	
	public static int windowsPopTimeLimit = 1000;
	
	/**
	 * The type can be null
	 * */
	public RESTBasedRecommender(String mainClassName, String[] args, String label, String type) {
		Utils.checkNotNull(mainClassName);
		Utils.checkNotNull(args);
		Utils.checkNotNull(label);
		this.mainClassName = mainClassName;
		this.args = args;
		try {
		    mainClass = Class.forName(mainClassName);
		} catch (Throwable e) {
			throw new Error(e);
		}
		this.label = label;
		this.type = type;
		//set the time limit
		UISpec4J.setWindowInterceptionTimeLimit(windowsPopTimeLimit);
	}
	
	public List<UIComponent> findGUIElements() {
		Panel mainWindow = this.getFreshMainWindow();
		return findGUIElements(mainWindow);
	}
	
	public List<UIComponent> findGUIElements(Panel panel) {
		final List<UIComponent> matchedComps = new LinkedList<UIComponent>();
		Collection<UIComponent> uics = Spec4JUtils.getAllGUIElements(Collections.singletonList(panel));
		
		Set<Class<? extends UIComponent>> componentClasses = new LinkedHashSet<Class<? extends UIComponent>>();
		for(UIComponent uic : uics) {
			componentClasses.add(uic.getClass());
			System.out.println(Spec4JUtils.getLabel(uic) + " ---- " + uic.getClass());
			String uicLabel = Spec4JUtils.getLabel(uic);
			if(uicLabel == null) {
				continue;
			}
			if(uicLabel.trim().equals(this.label)) {
				matchedComps.add(uic);
			}
		}
		
		if(!matchedComps.isEmpty()) {
			return matchedComps;
		}
		
		//explore the remaining actions
		List<UIEvent> eventQueue = new LinkedList<UIEvent>();
		for(UIComponent uic : uics) {
			if(!uic.isEnabled().isTrue()) {
				continue;
			}
			if(uic instanceof MenuItem) {
				MenuItem item = (MenuItem)uic;
				Collection<UIEvent> events = UIEventFactory.createAllEvents(item, panel, UIMenuPos.make(new String[]{"path"}));
				eventQueue.addAll(events);
			}
		}
		for(Class<? extends UIComponent> c : componentClasses) {
			if(c.equals(MenuItem.class)) {
				continue;
			} else {
				UIComponent[] comps = panel.getUIComponents(c);
				for(int index = 0; index < comps.length; index++) {
					UIComponent uic = comps[index];
					if(uic == null) {
						continue;
					}
					if(!uic.isEnabled().isTrue()) {
						continue;
					}
					UIPos pos = UIPos.make(index);
					Collection<UIEvent> events = UIEventFactory.createAllEvents(uic, panel, pos);
					eventQueue.addAll(events);
				}
				
			}
		}
		
		//execute each event
		for(UIEvent e : eventQueue) {
			if(!e.canFireEvent()) {
				continue;
			}
			System.out.println(e);
			WINDOW w = e.probeWindowInfo();
			UIEventOutcome outcome = e.fireEvent();
			System.out.println("  fire event: " + e);
			if(w.equals(WINDOW.MODAL)) {
				try {
				    WindowInterceptor.init(outcome.getTrigger()).process(new WindowHandler() {
			          public Trigger process(Window dialog) {
			        	  
			        	  List<UIComponent> newMatchedComps = findGUIElements(dialog);
						  matchedComps.addAll(newMatchedComps);
						
						  
			        	  return Trigger.DO_NOTHING;
			          }}).run();
				} catch (Throwable excep) {
					//swallow it
				}
			} else if (w.equals(WINDOW.NONMODAL)) {
				Window newPanel = WindowInterceptor.run(outcome.getTrigger());
				//recursive call
				List<UIComponent> newMatchedComps = this.findGUIElements(newPanel);
				matchedComps.addAll(newMatchedComps);
			}
		}
		
		
		return matchedComps;
	}
	
	private Panel getFreshMainWindow() {
		return MainWindowCreator.getFreshMainWindow(this.mainClass, this.args);
	}
	
	/**
	 * For testing purpose
	 * */
	public static void main(String[] args) {
		String mainClass = "crosswordsage.MainScreen";
		args = new String[]{};
		RESTBasedRecommender recommender = new RESTBasedRecommender(mainClass, args, "Solve New Word", null);
		List<UIComponent> comps = recommender.findGUIElements();
		
		System.out.println("Results: ");
		for(UIComponent c : comps) {
			System.out.println("   " + Spec4JUtils.getLabel(c));
		}
		
		System.exit(1);
	}
}